package civaid;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.jdo.JDOException;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;

import civaid.datamodel.Civilization;
import civaid.datamodel.CivilizationEvent;
import civaid.datamodel.Session;
import civaid.datamodel.Civilization.CARD_ID;
import civaid.datamodel.Civilization.CIVILIZATION_ID;

public class DatabaseService extends JDODatabaseService {

	public static DatabaseService getDB() {
		return new DatabaseService();
	}

	public Session getSession(String sessionId) {

		return (Session) executeOperation(new GetSessionOperation(sessionId, true));
	}

	public Session createOrGetSession(String sessionId, String password) {
		return (Session) executeOperation(new GetOrCreateSessionOperation(sessionId, password, true));
	}

	public Civilization getOrCreateCivilization(Session session, CIVILIZATION_ID civid) {

		return (Civilization) executeOperation(new GetOrCreateCivilizationOperation(session, civid, true));
	}


	
	
	/**
	 * Operation which fetches all civilization information for given session.
	 * 
	 * 
	 */
	class QueryAllCivilizationsInformationOperation extends DBOperation<Map<CIVILIZATION_ID, CivilizationDTO>> {

		protected Session session;

		public QueryAllCivilizationsInformationOperation(Session session, boolean useTransaction) {
			super(useTransaction);
			this.session = session;
		}

		protected CivilizationDTO copyCivilizationValues(Civilization civilization) {
			CivilizationDTO civ = new CivilizationDTO();
			civ.setPlayer(civilization.getPlayer());
			civ.setAstValuePoints(civilization.getAstValuePoints());
			civ.setCitiesPoints(civilization.getCitiesPoints());
			civ.setTreasuryPoints(civilization.getTreasuryPoints());
			civ.setTradeGoodsPoints(civilization.getTradeGoodsPoints());
			return civ;
		}
		
		protected Set<CARD_ID> getCurrentCards(Collection<CivilizationEvent> civilizationEvents) {
			Set<CARD_ID> cards = new HashSet<CARD_ID>();

			for (CivilizationEvent event : civilizationEvents) {

				Collection<CARD_ID> addedCards = (Collection<CARD_ID>) event.getAdded();
				Collection<CARD_ID> removedCards = (Collection<CARD_ID>) event.getRemoved();

				if (addedCards != null)
					for (CARD_ID cid : addedCards) {
						cards.add(cid);
						log.fine("added card: " + cid);
					}
				if (removedCards != null)
					for (CARD_ID cid : removedCards) {
						cards.remove(cid);
						log.fine("removed card: " + cid);
					}
			}
			return Collections.unmodifiableSet(cards);
		}

		@Override
		protected Map<CIVILIZATION_ID, CivilizationDTO> doIt(JDODatabaseService db, PersistenceManager pm) {

			Map<CIVILIZATION_ID, CivilizationDTO> civilizationsData = new HashMap<CIVILIZATION_ID, CivilizationDTO>();
			
			session = (Session) pm.getObjectById(Session.class, session.getKey());
			
			for (Civilization civilization : session.getCivilizations()) {
				Collection<CivilizationEvent> events = (Collection<CivilizationEvent>) executeOperation(new QueryCivilizationEventsOperation(civilization, false), pm);
				
				CivilizationDTO dto = copyCivilizationValues(civilization);
				if (events!=null) {
					dto.setCards(getCurrentCards(events));
				}
				civilizationsData.put(civilization.getCivilizationId(), dto);

			}
			return civilizationsData;
		
		}
	}

	/**
	 * Queries civilization information for given session
	 * 
	 * @param session
	 * @return map of values for each civilization
	 */
	public Map<CIVILIZATION_ID, CivilizationDTO> queryCivilizationInformation(Session session) {

		return (Map<CIVILIZATION_ID, CivilizationDTO>) executeOperation(new QueryAllCivilizationsInformationOperation(session, true));

	}

	/**
	 * Operation which fetches all civilization events for given session.
	 * 
	 * 
	 */
	class QueryAllCivilizationsEventsOperation extends DBOperation<Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>>> {

		protected Session session;

		public QueryAllCivilizationsEventsOperation(Session session, boolean useTransaction) {
			super(useTransaction);
			this.session = session;
		}

		protected CivilizationEventDTO copyCivilizationEventValues(CivilizationEvent event) {
			CivilizationEventDTO eventDTO = new CivilizationEventDTO();
			eventDTO.setAdded(event.getAdded());
			eventDTO.setRemoved(event.getRemoved());
			eventDTO.setCards(event.getCards());
			eventDTO.setDate(event.getDate());
			eventDTO.setPoints(event.getPoints());
			return eventDTO;
		}
		
		@Override
		protected Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> doIt(JDODatabaseService db, PersistenceManager pm) {

			Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> civilizationsData = new HashMap<CIVILIZATION_ID, Collection<CivilizationEventDTO>>();

			session = (Session) pm.getObjectById(Session.class, session.getKey());
			
			for (Civilization civilization : session.getCivilizations()) {
				Collection<CivilizationEvent> events = (Collection<CivilizationEvent>) executeOperation(
						new QueryCivilizationEventsOperation(civilization, false), pm);

				if (events != null) {
					Collection<CivilizationEventDTO> dtos = new ArrayList<CivilizationEventDTO>();
					for (CivilizationEvent event: events) {
						dtos.add(copyCivilizationEventValues(event));
					}
					civilizationsData.put(civilization.getCivilizationId(), dtos);
				}

			}
			return civilizationsData;
		}

	}

	/**
	 * Queries civilization events for given session
	 * 
	 * @param session
	 * @return map of values for each civilization
	 */
	public Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> queryCivilizationEvents(Session session) {

		return (Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>>) executeOperation(new QueryAllCivilizationsEventsOperation(
				session, true));

	}

	/**
	 * Stores civilization properties
	 * 
	 * @param session
	 * @param civilizationsData
	 *            , map of values for each civilization
	 * @return status
	 */
	public boolean storeCivilizationInformation(Session session, Map<CIVILIZATION_ID, CivilizationDTO> civilizationsData) {

		log.info("storing civilization information to session: " + session.getSessionId());

		return (Boolean) executeOperation(new StoreCivilizationInformationOperation(session, civilizationsData, true));
	}

	/**
	 * Stores civilization events
	 * 
	 * @param session
	 * @param civilizationsData
	 *            , map of events for each civilization
	 * @return
	 */
	public boolean storeCivilizationEvents(Session session, Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> civilizationsData) {

		log.info("storing civilization events to session: " + session.getSessionId());

		return (Boolean) executeOperation(new StoreCivilizationEventsOperation(session, civilizationsData, true));

	}

	/***
	 * Operation which gets session using session id
	 * 
	 */
	class GetSessionOperation extends DBOperation<Session> {

		protected String sessionId;

		public GetSessionOperation(String sessionId, boolean useTransaction) {
			super(useTransaction);
			this.sessionId = sessionId;
		}

		@Override
		protected Session doIt(JDODatabaseService db, PersistenceManager pm) {
			Query q = pm.newQuery(Session.class);
			q.setFilter("sessionId==id");
			q.setUnique(true);
			q.declareParameters("String id");
			Session s = (Session) q.execute(sessionId);
			return s;
		}
	}

	/**
	 * Operation which gets session, if session not found creates new
	 * 
	 */
	class GetOrCreateSessionOperation extends GetSessionOperation {

		protected String password;

		public GetOrCreateSessionOperation(String sessionId, String password, boolean useTransaction) {
			super(sessionId, useTransaction);
			this.password = password;
		}

		@Override
		protected Session doIt(JDODatabaseService db, PersistenceManager pm) {
			Session s = super.doIt(db, pm);

			if (s == null) {
				s = new Session(sessionId);
				s.markAsNew();
				s.setPassword(password);
				pm.makePersistent(s);
				log.info("created new session: " + s.getSessionId());
			}
			return s;
		}

	}

	/**
	 * Operation which gets civilization using session and civilization id
	 * 
	 */
	class GetCivilizationOperation extends DBOperation<Civilization> {

		protected Session session;
		protected CIVILIZATION_ID civid;

		public GetCivilizationOperation(Session session, CIVILIZATION_ID civid, boolean useTransaction) {
			super(useTransaction);
			this.session = session;
			this.civid = civid;
		}

		@Override
		protected Civilization doIt(JDODatabaseService db, PersistenceManager pm) {
			Query q = pm.newQuery(Civilization.class);
			q.setFilter("session==s && civilizationId==civid");
			q.setUnique(true);
			q.declareParameters("Session s, String civid");
			q.declareImports("import civaid.datamodel.*");
			Civilization c = (Civilization) q.execute(session, civid);
			return c;
		}
	}

	/**
	 * Operation which gets civilization, if civilization not found creates new
	 * 
	 */
	class GetOrCreateCivilizationOperation extends GetCivilizationOperation {

		public GetOrCreateCivilizationOperation(Session session, CIVILIZATION_ID civid, boolean useTransaction) {
			super(session, civid, useTransaction);
		}

		@Override
		protected Civilization doIt(JDODatabaseService db, PersistenceManager pm) {
			Civilization c = super.doIt(db, pm);
			if (c == null) {
				log.info("created new civilization to session: " + session.getSessionId() + " civid: " + civid);
				c = new Civilization(civid);
				session = (Session) pm.getObjectById(Session.class, session.getKey());
				c.setSession(session);
				pm.makePersistent(c);
			}
			return c;

		}
	}

	class QueryCivilizationEventsOperation extends DBOperation<Collection<CivilizationEvent>> {

		protected Civilization civilization;

		public QueryCivilizationEventsOperation(Civilization civilization, boolean useTransaction) {
			super(useTransaction);
			this.civilization = civilization;
		}

		@Override
		protected Collection<CivilizationEvent> doIt(JDODatabaseService db, PersistenceManager pm) {
			Query q = pm.newQuery(CivilizationEvent.class);
			q.setFilter("civilization==civ");
			q.declareParameters("Civilization civ");
			q.declareImports("import civaid.datamodel.*");
			q.setOrdering("date ascending");
			Collection<CivilizationEvent> s = (Collection<CivilizationEvent>) q.execute(civilization);
			if (s == null)
				s = new ArrayList<CivilizationEvent>();
			return s;
		}
	}

	class StoreCivilizationEventsOperation extends DBOperation<Boolean> {

		protected Session session;
		private Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> civilizationsData;

		public StoreCivilizationEventsOperation(Session session, Map<CIVILIZATION_ID, Collection<CivilizationEventDTO>> civilizationsData,
				boolean useTransaction) {
			super(useTransaction);
			this.session = session;
			this.civilizationsData = civilizationsData;

		}

		protected void storeCivilizationEventValues(CivilizationEvent event, CivilizationEventDTO eventValues) {
			event.setAdded(eventValues.getAdded());
			event.setRemoved(eventValues.getRemoved());
			event.setCards(eventValues.getCards());
			event.setDate(new Date(eventValues.getDate()));
			event.setPoints(eventValues.getPoints());
		}
		
		@Override
		protected Boolean doIt(JDODatabaseService db, PersistenceManager pm) {

			for (CIVILIZATION_ID civid : civilizationsData.keySet()) {
				Collection<CivilizationEventDTO> events = civilizationsData.get(civid);

				if (events.isEmpty()) continue;
				
				Civilization civilization = (Civilization) db.executeOperation(new GetOrCreateCivilizationOperation(session, civid, false),
						pm);

				Collection<CivilizationEvent> existingEvents = civilization.getCivilizationEvents();
				Set<String> existingIds = new HashSet<String>();
				for (CivilizationEvent existingEvent : existingEvents) {
					log.info("EXISTING LIST: "+existingEvent.getIdentification());
					existingIds.add(existingEvent.getIdentification());
				}

				for (CivilizationEventDTO eventDTO : events) {
					CivilizationEvent event = new CivilizationEvent();
					storeCivilizationEventValues(event, eventDTO);
					
					if (existingIds.contains(event.getIdentification())) {
						log.info("skipped existing event: " + event.getIdentification());
						continue;
					}
					else {
						log.info("does not exist"+event.getIdentification());
					}
					event.setCivilization(civilization);
					pm.makePersistent(event);
				}
			}
			return true;
		}

	}

	class StoreCivilizationInformationOperation extends DBOperation<Boolean> {

		protected Session session;
		protected Map<CIVILIZATION_ID, CivilizationDTO> civilizationsData;

		public StoreCivilizationInformationOperation(Session session, Map<CIVILIZATION_ID, CivilizationDTO> civilizationsData,
				boolean useTransaction) {
			super(useTransaction);
			this.session = session;
			this.civilizationsData = civilizationsData;
		}

		protected void storeCivilizationValues(Civilization civilization, CivilizationDTO civilizationValues) {
			
			civilization.setPlayer(civilizationValues.getPlayer());
			civilization.setAstValuePoints(civilizationValues.getAstValuePoints());
			civilization.setCitiesPoints(civilizationValues.getCitiesPoints());
			civilization.setTreasuryPoints(civilizationValues.getTreasuryPoints());
			civilization.setTradeGoodsPoints(civilizationValues.getTradeGoodsPoints());
			
		}

		protected boolean isActive(CivilizationDTO civilizationValues) {
			return civilizationValues.getPlayer()!=null && !civilizationValues.getPlayer().isEmpty();
		}
		
		@Override
		protected Boolean doIt(JDODatabaseService db, PersistenceManager pm) {

			for (CIVILIZATION_ID civid : civilizationsData.keySet()) {
				CivilizationDTO civilizationValues = civilizationsData.get(civid);

				if (isActive(civilizationValues)) {
				
					Civilization civilization = (Civilization) db.executeOperation(new GetOrCreateCivilizationOperation(session, civid, false),pm);
					log.info("active civilization: "+civid);
	
					storeCivilizationValues(civilization, civilizationValues);
				}
				else {
					Civilization civilization = (Civilization) db.executeOperation(new GetCivilizationOperation(session, civid, false),pm);
					if (civilization!=null) {
						if (civilization.getCivilizationEvents().isEmpty()) {
							log.info("deleted empty civilization: "+civid);
							pm.deletePersistent(civilization);
						}
						else {
							storeCivilizationValues(civilization, civilizationValues);
						}
					}
				}

			}
			return true;
		}
	}
}
